<template>
  <view class="debug-console">
    <!-- 滑动区域 -->
    <movable-area
        v-show="!showPanel"
        class="movablearea_index"
        :style="'height: ' + movableareHeight + 'px; top: ' + movableareTop + 'px;'"
    >
        <movable-view
            :x="x"
            :y="y"
            direction="all"
            @touchend="bindtouchend"
            @change="movablechange"
            @touchstart="bindtouchstart"
            class="debug-btn"
        >
          VConsole
        </movable-view>
    </movable-area>
    <!-- 信息弹窗 -->
    <view class="console-panel_container" @touchmove.stop.prevent v-if="showPanel" @click="togglePanel">
      <view class="console-panel" @click.stop="opencConsole">
        <!-- 信息类型 -->
        <view class="toolbar">
          <view class="tab" :class="{active: logLevel === 'all'}" @click.stop="logLevel = 'all'">log</view>
          <view class="tab" :class="{active: logLevel === 'warn'}" @click.stop="logLevel = 'warn'">Warn</view>
          <view class="tab" :class="{active: logLevel === 'error'}" @click.stop="logLevel = 'error'">Error</view>
          <view class="tab" :class="{active: logLevel === 'storage'}" @click.stop="logLevel = 'storage'">Storage</view>
          <view class="tab" :class="{active: logLevel === 'api'}" @click.stop="logLevel = 'api'">Api</view>
          <view class="tab" :class="{active: logLevel === 'system'}" @click.stop="logLevel = 'system'">System</view>
        </view>
        <!-- 信息内容 -->
        <scroll-view class="log-content" scroll-y @touchmove.stop.prevent>
          <!-- log 信息 -->
          <template v-if="logLevel === 'all'">
            <view v-for="(log, index) in filteredLogs" :key="index" :class="index == filteredLogs.length - 1 ? 'log-item last' : 'log-item'" @click.stop="copy(logLevel,log.content)">
              <text class="time">{{ log.time }}</text>
              <text :class="['level', log.level]">[{{ log.level.toUpperCase() }}]</text>
              <text class="message">{{ log.content }}</text>
              <view class="copy" @click.stop="copy(logLevel,log.content)">复制</view>
            </view>
          </template>
          <!-- 缓存 -->
          <template v-if="logLevel === 'storage'">
            <view v-for="(item, index) in storageLogs" :key="'storage'+index" :class="index == storageLogs.length - 1 ? 'log-item last' : 'log-item'" @click.stop="copy(logLevel,item)">
              <!-- <text class="time">{{ item.time }}</text> -->
              <text class="level">[STORAGE]</text>
              <text class="message">{{ item.key }}: {{ item.value }}</text>
              <view class="copy" @click.stop="copy(logLevel,item)">复制</view>
            </view>
          </template>
          <!-- api -->
          <template v-if="logLevel === 'api'">
            <view v-for="(item, index) in apiLogs" :key="'api'+index" :class="index == apiLogs.length - 1 ? 'log-item last' : 'log-item'" @click.stop="copy(logLevel,item)">
              <view class="level_title">
                [Url]：{{ item.url }}
              </view>
              <view>
                <view class="level_title">Request: </view>
                <view class="level">[method]：{{ item.method }}</view>
                <view class="level">[Header]：{{ JSON.stringify(item.header) }}</view>
                <view class="level">[Data]：{{ item.data }}</view>
              </view>
              <view>
                <view class="level_title">Response: </view>
                <view class="level">[StatusCode]：{{ item.response.statusCode }}</view>
                <view class="level">[Data]：{{ item.response.data }}</view>
              </view>
              <view>
                <view class="level">[Time]：{{ item.time }} duration: {{item.duration}}ms</view>
              </view>
              <view class="copy" @click.stop="copy(logLevel,item)">复制</view>
            </view>
          </template>
          <!-- 系统信息 -->
          <template v-if="logLevel === 'system'">
            <view
              v-for="([key, val], index) in Object.entries(deviceInfo)"
              :key="key"
              :class="index === Object.entries(deviceInfo).length - 1 ? 'log-item last' : 'log-item'"
              @click.stop="copy(logLevel, key)"
            >
              <text class="level">{{ key }}</text>: <text class="message">{{ JSON.stringify(val) }}</text>
              <view class="copy" @click.stop="copy(logLevel, key)">复制</view>
            </view>
          </template>
          <view class="placeholder_box"></view>
        </scroll-view>
        <view class="console-footer">
          <view class="clear" @click.stop="clearLogs">清空</view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
const deviceInfo = uni.getSystemInfoSync()
const px = (deviceInfo.screenWidth / 750).toFixed(2);
const x = deviceInfo.screenWidth - 180 * px - 10;
const y = (1000 * px).toFixed(1);
export default {
  data() {
    return {
      showPanel: false, // 显示弹窗
      logLevel: 'all',
      storageLogs: [], // 缓存信息
      apiLogs: [], // api信息
      deviceInfo: deviceInfo, // 系统信息
      logs: [], // 日志信息
      x: x, // 浮窗x坐标
      y: y, // 浮窗y坐标
      movableareHeight: 0,
      movableareTop: 0,
    }
  },
  computed: {
    filteredLogs() {
      const logs = this.logs || []
      return logs.filter(log => this.logLevel === 'all' || log.level === this.logLevel)
    }
  },
  onShow() {
  },
  mounted() {
      this.hijackRequest() // 劫持请求
      let movableareTop = deviceInfo.statusBarHeight * 2 
      let movableareHeight = 0;
      movableareHeight = deviceInfo.screenHeight;
      this.movableareHeight = movableareHeight;
      this.movableareTop = movableareTop;
  },
  methods: {
    opencConsole() {
      console.log('open console')
    },
    // 开启关闭弹窗
    togglePanel() {
      this.isStop = true;
      if(!this.showPanel){
        this.logLevel = 'all';
        this.loadStorageData();
        this.deviceInfo = deviceInfo;
      }
      this.showPanel = !this.showPanel
    },
    // 清空日志
    clearLogs() {
      if(this.logLevel === 'all'){
        this.logs = []
      }else if(this.logLevel === 'storage'){
        this.storageLogs = []
      }else if(this.logLevel === 'api'){
        this.apiLogs = []
      }else if(this.logLevel === 'system'){
        this.deviceInfo = {}
      }
    },
    // 获取当前时间
    getCurrentTime() {
      const now = new Date()
      return `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`
    },
    // 获取缓存信息
    loadStorageData() {
      try {
        const storageInfo = uni.getStorageInfoSync()
        const allKeys = storageInfo.keys || []
        this.storageLogs = allKeys.map(key => {
          let value
          try {
            value = uni.getStorageSync(key)
          } catch (e) {
            value = '[读取失败]'
          }
          return {
            key,
            value,
            time: this.getCurrentTime()
          }
        })
      } catch (e) {
        console.error('获取缓存信息失败:', e)
      }
    },
    // 劫持请求
    hijackRequest() {
      this.apiLogs = []
      uni.addInterceptor('request', {
        invoke:(args)=> {
          const start = Date.now()
          // request 触发前拼接 url
          if(args.complete){
            // 将原始的 complete 方法保存到 originalComplete 变量中
            const originalComplete = args.complete
            // 重写 complete 方法
            args.complete = (res) => {
              // 记录请求的相关信息
              this.apiLogs.push({
                url: args.url,
                method: args.method,
                header: args.header,
                data: args.data,
                time: this.getCurrentTime(),
                duration: Date.now() - start,
                response: {
                  statusCode: res.statusCode,
                  data: res.data,
                }
              })
              // 如果用户原本传了 complete，记得调用回去
              return originalComplete(res)
            }
          }
        },
      })
    },
    addLog(level, ...args) {
      // 获取当前时间
      const now = new Date()
      // 将日志添加到logs数组中
      this.logs.push({
        // 日志时间
        time: `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`,
        // 日志级别
        level,
        // 日志内容
        content: this.formatArgs(args)
      })
      // 如果logs数组长度超过200，则删除第一个元素
      if (this.logs.length > 200) this.logs.shift()
    },
    formatArgs(args) {
      // 使用map方法遍历参数数组
      return args.map(arg => {
        // 尝试将参数转换为字符串
        try {
          // 如果参数是对象，则使用JSON.stringify方法将其转换为字符串
          if (typeof arg === 'object') return JSON.stringify(arg, null, 2)
          // 否则直接将参数转换为字符串
          return String(arg)
        } catch (e) {
          // 如果转换失败，则返回'[Circular]'
          return '[Circular]'
        }
      }).join(' ')
    },
    log(...args) {
      this.addLog('log', ...args)
    },
    warn(...args) {
      this.addLog('warn', ...args)
    },
    error(...args) {
      this.addLog('error', ...args)
    },
    // 浮窗拖动事件
    movablechange(e) {
      this.isMoving = true // 初始状态是没移动
      if (!this.isStop) {
          // 记录浮窗拖动最后y轴位置
          this.detailY = e.detail.y;
          this.detailX = e.detail.x;
      }
    },
    // 浮窗拖动开始
    bindtouchstart(e) {
      this.isMoving = false // 初始状态是没移动
      this.isStop = false;
    },

    // 浮窗拖动结束
    bindtouchend() {
      this.isStop = true;
      if(!this.isMoving){
        this.togglePanel()
      }else{
        this.x = this.detailX;
        this.y = this.detailY;
      }
    },
    // 复制信息
    copy(type,data){
      let copyText = '';
      if(type == 'all'){
        copyText = data;
      }else if(type == 'storage'){
        copyText = `${data.key}: ${typeof data.value === 'object' ? JSON.stringify(data.value) : data.value}`;
      }else if(type == 'api'){
        copyText = JSON.stringify(data);
      }else if(type == 'system'){
        let deviceInfo = this.deviceInfo;
        copyText = `${data}: ${typeof deviceInfo[data] === 'object' ? JSON.stringify(deviceInfo[data]) : deviceInfo[data]}`;
      }
      uni.setClipboardData({
        data: copyText,
        success: () => {
            uni.showToast({
              title: "复制成功",
              icon: "none"
            });
        },
        fail: () => {
            uni.showToast({
            icon: "none"
            });
        }
        });
        }
  }
}
</script>

<style scoped>
.debug-btn {
  position: fixed;
  right: 20px;
  bottom: 20px;
  width: 180rpx;
  /* padding: 0 20rpx; */
  height: 50rpx;
  line-height: 50rpx;
  text-align: center;
  border-radius: 10rpx;
  background-color: #41b983;
  color: white;
  z-index: 99999;
  font-weight: bold;
  pointer-events: auto;
  box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.2);
}

.console-panel_container{
  width: 100%;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99999;
  box-sizing: border-box;
  padding-top: 10vh;
  background-color: rgba(0, 0, 0, 0.5);
}
.console-panel {
  position: absolute;
  bottom: 0;
  width: 100vw;
  height: 80vh;
  background-color: #f9f9f9;
  z-index: 9998;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
  display: flex;
  flex-direction: column;
}

.toolbar {
  padding: 10px 0;
  display: flex;
  /* gap: 10rpx; */
  box-sizing: border-box;
  border-bottom: 1px solid #ddd;
  padding-left: 10rpx;
}

.tab {
  padding: 0rpx 20rpx;
  height: 60rpx;
  line-height: 60rpx;
  border-radius: 10rpx;
  background: #eee;
  margin-right: 5rpx;
  color: #333;
}

.tab.active {
  background: #41b983;
  color: #fff;
}

.log-content {
  flex: 1;
  padding: 10px;
  overflow-y: auto;
}

.log-item {
  font-size: 12px;
  font-family: monospace;
  margin-bottom: 6px;
  word-break: break-word;
  border-bottom: 1px solid #000;
  padding-bottom: 10rpx;
  position: relative;
  min-height: 60rpx;
}
.copy{
  position: absolute;
  right: 0;
  top: 0;
  width: 60rpx;
  height: 40rpx;
  line-height: 40rpx;
  text-align: center;
  border-radius: 4rpx;
  background: #41b983;
  margin-right: 5rpx;
  color: #fff;
}
.copy:active{
  background: #41b983;
  color: #fff;
}
.log-item>view{
  margin-bottom: 6px;
}
.log-item.last{
  border-bottom: none;
}
.time {
  color: #888;
  margin-right: 6px;
}


.level {
  font-weight: bolder;
}
.level.log {
  color: #000;
}

.level.warn {
  color: #e6a23c;
}

.level.error {
  color: #f56c6c;
}
.console-footer{
  width: 100vw;
  height: 60rpx;
  line-height: 60rpx;
  text-align: center;
  border-top: 1px solid #ddd;
  position: absolute;
  bottom: 0;
  left: 0;
  background: #FFF;
  display: flex;
  justify-content: flex-start;
}
.clear{
  color: #f00;
  padding: 0rpx 20rpx;
  height: 60rpx;
  line-height: 60rpx;
  border-radius: 4rpx;
  /* background: #eee; */
  margin-right: 5rpx;
  width: fit-content;
}
.placeholder_box{
  width: 100%;
  height: 100rpx;
}
.movablearea_index {
    width: 100vw;
    pointer-events: none;
    position: fixed;
    left: 0;
    z-index: 99999;
    transform: translateX(0rpx);
    transition: all 0.5s;
}
.movablearea_index.slide {
    transform: translateX(84rpx);
    transition: all 1s;
}
.movable_view_index {
    pointer-events: auto;
    width: 120rpx;
    height: 120rpx;
    background: transparent;
    /* box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(79,0,0,0.2); */
}
.level_title{
  font-size: 26rpx;
  font-weight: bolder;
}
</style>